package com.caishi.lkx.handler;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.caishi.lkx.common.context.UserContext;
import com.caishi.lkx.common.unionpay.sdk.SecureUtil;
import com.caishi.lkx.exam.ienum.type.PkStatus;
import com.caishi.lkx.exam.ienum.type.PkType;
import com.caishi.lkx.exam.ienum.type.QuestionType;
import com.caishi.lkx.exam.mapper.*;
import com.caishi.lkx.exam.model.*;
import com.caishi.lkx.exam.service.ICategoryService;
import com.caishi.lkx.exam.service.IExamService;
import com.caishi.lkx.exam.util.RandomSelection;
import com.caishi.lkx.exam.vo.PkData;
import com.caishi.lkx.exam.vo.RoomQuestionStateVo;
import com.caishi.lkx.order.ienum.type.AssetsType;
import com.caishi.lkx.order.model.UserAssetsDataModel;
import com.caishi.lkx.order.service.IUserAssetsDataService;
import com.caishi.lkx.user.mapper.UserMapper;
import com.caishi.lkx.user.model.UserModel;
import com.zzw.common.Wrappers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.*;


/**
 * 有重连与重新匹配两种可能，重新匹配删除原来的房间
 * 出于响应性能，减少io等原因【虽然会更消耗一些java内存】，将房间试题映射到内存中，随房间销毁而销毁
 */
@Component
public class PkWebSocketHandler extends TextWebSocketHandler {

    private static final String give_up_text = "放弃pk";
    private static final String notify_give_up_text = "您的对手弃权";
    private static final int per_question_second = 20;
    private static final Logger log = LoggerFactory.getLogger(PkWebSocketHandler.class);
    private final WebSocketMapper webSocketMapper;
    private final UserContext userContext;
    private final PkRoomMapper pkRoomMapper;
    // 最大连接数配置,从mysql中获取配置
    private final UserMapper userMapper;

    // 存储等待配对的用户
    private final Queue<WebSocketSession> waitingPlayers = new ConcurrentLinkedQueue<>();
    // 存储每个session的最后活动时间
    private final Map<WebSocketSession, Long> sessionLastActiveTime = new ConcurrentHashMap<>();
    // 心跳超时时间（毫秒），例如1分钟
    private static final long HEARTBEAT_TIMEOUT = 1 * 60 * 1000; // 1分钟
    // 定时任务用于检查超时连接
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(30);
    private final QuestionMapper questionMapper;
    private final CategoryMapper categoryMapper;
    private final PkRecordsMapper pkRecordsMapper;
    //    50个房间，每个房间30题，所占内存还是挺大的
    private Map<Integer, Map<String, RoomQuestionStateVo>> roomQuestionStateMap = new ConcurrentHashMap<>();
    private final IUserAssetsDataService userAssetsDataService;
    private final ICategoryService categoryService;
    private final IExamService examService;

    private final Map<Integer, Object> roomLocks = new ConcurrentHashMap<>();

    private Object getRoomLock(Integer roomId) {
        return roomLocks.computeIfAbsent(roomId, k -> new Object());
    }

    public PkWebSocketHandler(WebSocketMapper webSocketMapper, UserContext userContext, PkRoomMapper pkRoomMapper, UserMapper userMapper, QuestionMapper questionMapper, CategoryMapper categoryMapper, PkRecordsMapper pkRecordsMapper, IUserAssetsDataService userAssetsDataService, ICategoryService categoryService, IExamService examService) {
        this.webSocketMapper = webSocketMapper;
        this.userContext = userContext;
        this.pkRoomMapper = pkRoomMapper;
        this.userMapper = userMapper;
        this.questionMapper = questionMapper;
        this.categoryMapper = categoryMapper;
        this.pkRecordsMapper = pkRecordsMapper;
        this.userAssetsDataService = userAssetsDataService;
        this.categoryService = categoryService;
        this.examService = examService;
    }


    public Queue<WebSocketSession> getWaitingPlayers() {
        return waitingPlayers;
    }

    @PostConstruct
    public void init() {
//        删除之前的匹配房间
        QueryWrapper<PkRoom> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNotNull("id");
        pkRoomMapper.delete(queryWrapper);
        // 启动定时检查任务，每30秒检查一次超时连接
        scheduler.scheduleAtFixedRate(this::checkTimeoutConnections, 30, 30, TimeUnit.SECONDS);
    }

    @PreDestroy
    public void destroy() {
        scheduler.shutdown();
    }

    // 检查超时连接的方法
    private void checkTimeoutConnections() {
        long currentTime = System.currentTimeMillis();
        Iterator<Map.Entry<WebSocketSession, Long>> iterator = sessionLastActiveTime.entrySet().iterator();

        while (iterator.hasNext()) {
            Map.Entry<WebSocketSession, Long> entry = iterator.next();
            WebSocketSession session = entry.getKey();
            Long lastActiveTime = entry.getValue();
            String userId = (String) session.getAttributes().get("userId");
            String examId = (String) session.getAttributes().get("examId");

            // 检查是否超时
            if (currentTime - lastActiveTime > HEARTBEAT_TIMEOUT) {
                try {
                    // 超时，关闭连接,当不聚焦于页面，会不发送请求，导致超时
                    if (session.isOpen()) {
                        session.close(CloseStatus.GOING_AWAY.withReason("Heartbeat timeout"));
                    }
                } catch (IOException e) {
                    // 记录日志
                    e.printStackTrace();
                }
            }
        }
    }

    private Map<String, Object> transQuestionToMap(QuestionModel question) {
        HashMap<String, Object> stringObjectHashMap = new HashMap<>();
        stringObjectHashMap.put("id", question.getId());
        stringObjectHashMap.put("categoryId", question.getCategoryId());
        stringObjectHashMap.put("answerDesc", question.getAnswerDesc());
        stringObjectHashMap.put("answer", question.getAnswer());
        stringObjectHashMap.put("firstType", question.getFirstType());
        stringObjectHashMap.put("stem", question.getStem());
        stringObjectHashMap.put("quOption", question.getQuOption());
        return stringObjectHashMap;
    }

    /**
     * 会有一些前置条件，符合条件的才能匹配
     * 用户加入匹配队列
     */
    public void addToMatchQueue(WebSocketSession session, UserModel userModel, String examId, PkType type) {
        session.getAttributes().put("userId", userModel.getId());
        session.getAttributes().put("examId", examId);
        session.getAttributes().put("type", type);
        PkRoom pkRoom = pkRoomMapper.getPkRoomOne(userModel.getId(), examId, null);
        if (pkRoom != null) {
            if (pkRoom.getSessionId1() == null || pkRoom.getSessionId2() == null) {
                if (pkRoom.getPerson1().equals(userModel.getId())) {
//                    正常
                } else {
//                    不正常
                }
            } else {
                throw new RuntimeException("请稍后重试");
            }
        }
//        查看旧队列是否包含有该用户的连接，限制一用户只能有一个连接
//        for (WebSocketSession waitingPlayer : waitingPlayers) {
//            if (waitingPlayer.getAttributes().get("userId").equals(userModel.getId())) {
//                try {
//                    waitingPlayer.close(CloseStatus.NORMAL.withReason("关闭多余的连接"));
//                    waitingPlayers.remove(waitingPlayer);
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//                break;
//            }
//        }

        waitingPlayers.offer(session);
        sessionLastActiveTime.put(session, System.currentTimeMillis());
        tryMatch(session, userModel, pkRoom);
    }

    // 判断是否为心跳消息
    private boolean isHeartbeatMessage(String message) {
        // 根据你的前端心跳消息格式来判断
        // 例如，心跳消息可能是 {"type":"heartbeat"} 或简单的 "ping"
        return message.equals("{\"type\":\"ping\"}");
    }

    /**
     * 同考试的玩家进行匹配,cover是重连的标志
     * 尝试匹配玩家
     * 极端情况下，双方不断开而再开一个连接匹配，导致生成房间在删除房间之前，导致删除房间异常
     */
    private void tryMatch(WebSocketSession session, UserModel userModel, PkRoom initialRoom) {
        String userId = (String) session.getAttributes().get("userId");
        String examId = (String) session.getAttributes().get("examId");
        PkType type = (PkType) session.getAttributes().get("type");
        //        为了体验，需要重连时恢复进入之前的房间
//         查库，如果有对应userId与examId。则视为之前的匹配
        PkRoom pkRoom = initialRoom;

        if (pkRoom != null) {
            session.getAttributes().put("roomId", pkRoom.getId());
//            重连通知对方
            UpdateWrapper<PkRoom> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("id", pkRoom.getId());
            boolean isPerson1 = false;
            String otherUserId = "";
            if (pkRoom.getPerson1().equals(userId)) {
                isPerson1 = true;
                pkRoom.setSessionId1(session.getId());
                otherUserId = pkRoom.getPerson2();
                updateWrapper.set("session_id1", session.getId());
            } else {
                pkRoom.setSessionId2(session.getId());
                otherUserId = pkRoom.getPerson1();
                updateWrapper.set("session_id2", session.getId());
            }
            pkRoomMapper.update(pkRoom, updateWrapper);
            session.getAttributes().put("otherUserId", otherUserId);
            for (WebSocketSession waitingPlayer : waitingPlayers) {
                if (waitingPlayer.getAttributes().get("examId").equals(examId) && waitingPlayer.getAttributes().get("userId").equals(otherUserId)) {
//                    如果这个时候对方还在线
                    if (waitingPlayer.isOpen()) {
                        JSONObject jsonObject1 = new JSONObject();
                        JSONObject jsonObject2 = new JSONObject();
//                        重连，重新获取对手基本信息
                        Map<String, Object> user = new HashMap<>();

                        if (isPerson1) {
                            user.put("id", pkRoom.getPerson2());
                            user.put("name", pkRoom.getPerson2Name());
                            user.put("header", pkRoom.getHeader2());
                        } else {
                            user.put("id", pkRoom.getPerson1());
                            user.put("name", pkRoom.getPerson1Name());
                            user.put("header", pkRoom.getHeader1());
                        }
                        jsonObject1.put("data", user);
                        jsonObject1.put("reConnect", true);
                        try {
//                            sendMessage(waitingPlayer, pkRoom.getId(), PkStatus.reconnect_success, type, jsonObject2);
                            synchronized (session) {
                                sendMessage(session, pkRoom.getId(), PkStatus.reconnect_success, type, jsonObject1);
                            }
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                        break;
                    }
//                    else {
//                        Map<String, Object> user = new HashMap<>();
//                        if (isPerson1) {
//                            user.put("id", pkRoom.getPerson2());
//                            user.put("name", pkRoom.getPerson2Name());
//                            user.put("header", pkRoom.getHeader2());
//                        } else {
//                            user.put("id", pkRoom.getPerson1());
//                            user.put("name", pkRoom.getPerson1Name());
//                            user.put("header", pkRoom.getHeader1());
//                        }
//                        JSONObject jsonObject = new JSONObject();
//                        jsonObject.put("message", "对方暂不在线");
//                        jsonObject.put("data", user);
//                        try {
//                            sendMessage(session, pkRoom.getId(), PkStatus.other_not_online, type, jsonObject);
//                        } catch (IOException e) {
//                            throw new RuntimeException(e);
//                        }
//                        break;
//                    }
                }
            }
            return;
        }
//        获取examId与userId

        if (waitingPlayers.size() >= 2) {
            Iterator<WebSocketSession> iterator = waitingPlayers.iterator();
            WebSocketSession player1 = session;
            WebSocketSession player2 = null;
            while (iterator.hasNext()) {
                WebSocketSession session1 = iterator.next();
//                遍历取出自己和符合条件的对手两个，同考试，不在房间中
                if (examId.equals(session1.getAttributes().get("examId")) && session1.getAttributes().get("roomId") == null) {
                    if (!(session1.getId().equals(session.getId()))) {
                        player2 = session1;
                        break;
                    }
                }
            }
            if (player2 != null && player1.isOpen() && player2.isOpen()) {

                QueryWrapper<CategoryModel> queryWrapper0 = new QueryWrapper<>();
                queryWrapper0.eq("exam_id", examId).eq("deleted", false).select("id");
                List<CategoryModel> categoryModels = categoryMapper.selectList(queryWrapper0);
                List<Long> categoryIds = categoryModels.stream().map(CategoryModel::getId).toList();

                QueryWrapper<QuestionModel> queryWrapper1 = new QueryWrapper<>();
                List<QuestionType> types = new ArrayList<>();
                types.add(QuestionType.singleChoice);
                types.add(QuestionType.multipleChoice);
//                types.add(QuestionType.judgment);
                queryWrapper1.in("category_id", categoryIds).in("first_type", types.stream().map(QuestionType::getCode).toList()).eq("deleted", false).select("id");

//                List<QuestionModel> questionModels = questionMapper.selectList(queryWrapper1);
                Page<QuestionModel> questionModelPage = questionMapper.selectPage(new Page<>(1, 1000), queryWrapper1);


                List<QuestionModel> questionModels = questionModelPage.getRecords();


                List<QuestionModel> questionModels1 = new ArrayList<>();
                if (questionModels.isEmpty()) {

                } else if (questionModels.size() < 15) {
                    questionModels1 = questionModels;
                } else {
                    questionModels1 = RandomSelection.randomSelect(questionModels, 15);
                }

                if (!questionModels1.isEmpty()) {
                    QueryWrapper<QuestionModel> queryWrapper2 = new QueryWrapper<>();
                    queryWrapper2.in("id", questionModels1.stream().map(QuestionModel::getId).toList());
                    questionModels1 = questionMapper.selectList(queryWrapper2);
                }


                int total = 0;
                List<Map<String, Object>> questionsList = new ArrayList<>();
                for (QuestionModel questionModel : questionModels1) {
                    if (questionModel.getFirstType() == QuestionType.multipleChoice) {
                        total += 2;
                    } else {
                        total += 1;
                    }
                    questionsList.add(transQuestionToMap(questionModel));
                }

                player1.getAttributes().put("otherUserId", player2.getAttributes().get("userId"));
                player2.getAttributes().put("otherUserId", player1.getAttributes().get("userId"));
                UserModel user1 = userModel;
                UserModel user2 = userMapper.selectById((String) player2.getAttributes().get("userId"));

                String sign = examId;
                int compare = ((String) player1.getAttributes().get("userId")).compareTo((String) player2.getAttributes().get("userId"));
                if (compare < 0) {
                    sign = sign.concat((String) player1.getAttributes().get("userId")).concat((String) player2.getAttributes().get("userId"));
                } else {
                    sign = sign.concat((String) player2.getAttributes().get("userId")).concat((String) player1.getAttributes().get("userId"));
                }
                // 创建PK房间
                PkRoom room = PkRoom.builder()
                        .examId(examId)
                        .score1(0)
                        .score2(0)
                        .sessionId1(player1.getId())
                        .sessionId2(player2.getId())
                        .total(total)
                        .type(type)
                        .questions(questionsList)
                        .doNum1(0)
                        .doNum2(0)

                        .person1((String) player1.getAttributes().get("userId"))
                        .person1Name(user1.getName())
                        .header1(user1.getHeader())
                        .person2((String) player2.getAttributes().get("userId"))
                        .person2Name(user2.getName())
                        .header2(user2.getHeader())
                        .sign(sign)


                        .build();
                int insert = 0;
//                由于关闭session再到删除房间需要耗时，如果这个时候重新匹配上，会导致再次插入一条相同的数据
//                防止并发插入,数据库唯一索引来约束
                try {
                    insert = pkRoomMapper.insert(room);
                } catch (Exception e) {
                    throw new RuntimeException("请勿重复创建房间");
                }

                if (insert > 0) {
//                       创建成功
                    JSONObject jsonObject1 = new JSONObject();
                    JSONObject jsonObject2 = new JSONObject();

                    JSONObject jsonObject11 = new JSONObject();
                    JSONObject jsonObject12 = new JSONObject();
                    jsonObject11.put("id", user1.getId());
                    jsonObject11.put("name", user1.getName());
                    jsonObject11.put("header", user1.getHeader());


                    jsonObject12.put("id", user2.getId());
                    jsonObject12.put("name", user2.getName());
                    jsonObject12.put("header", user2.getHeader());


                    jsonObject1.put("data", jsonObject11);
                    jsonObject2.put("data", jsonObject12);
                    player1.getAttributes().put("roomId", room.getId());
                    player2.getAttributes().put("roomId", room.getId());


                    Map<String, RoomQuestionStateVo> objectObjectConcurrentHashMap = new HashMap<>();

                    for (QuestionModel questionModel : questionModels1) {
                        RoomQuestionStateVo roomQuestionStateVo = new RoomQuestionStateVo();
                        roomQuestionStateVo.setQuestionId(questionModel.getId());
                        roomQuestionStateVo.setType(questionModel.getFirstType());
                        roomQuestionStateVo.setAnswer(questionModel.getAnswer());
                        roomQuestionStateVo.setPlayer1Id((String) player1.getAttributes().get("userId"));
                        roomQuestionStateVo.setPlayer2Id((String) player2.getAttributes().get("userId"));
//                        重点，设置超时任务
                        objectObjectConcurrentHashMap.put(questionModel.getId(), roomQuestionStateVo);
                    }
//                    并发添加会相互覆盖，没问题
                    roomQuestionStateMap.put(room.getId(), objectObjectConcurrentHashMap);
                    // 通知双方匹配成功
                    try {
                        sendMessage(player1, room.getId(), PkStatus.match_success, type, jsonObject1);
                        sendMessage(player2, room.getId(), PkStatus.match_success, type, jsonObject2);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }

                } else {
//                       极端情况下创建失败
                    try {
                        session.close(CloseStatus.NO_CLOSE_FRAME.withReason("创建房间失败"));
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }


    /**
     * 发送匹配成功消息
     */
    private void sendMessage(WebSocketSession session, Integer roomId, PkStatus status, PkType type, JSONObject data) throws IOException {

        JSONObject message1 = new JSONObject();
        message1.put("status", status.name());
        message1.put("roomId", roomId);
        message1.put("type", type.name());
        message1.put("data", data);
//        message1.put("data", data.toJSONString());
        session.sendMessage(new TextMessage(message1.toJSONString()));

    }


    /**
     * 后端不进行登录身份校验，如果没有这个用户，不连接
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        MultiValueMap<String, String> queryParams = UriComponentsBuilder.fromUri(Objects.requireNonNull(session.getUri()))
                .build()
                .getQueryParams();

        if (queryParams.get("userId") == null || queryParams.get("examId").isEmpty()) {
            session.close(CloseStatus.BAD_DATA.withReason("没有指定用户"));
            return;
        }
        if (queryParams.get("examId") == null || queryParams.get("examId").isEmpty()) {
            session.close(CloseStatus.BAD_DATA.withReason("没有指定考试"));
            return;
        }

        QueryWrapper<WebsocketModel> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("max_connect");

        WebsocketModel websocketModel = webSocketMapper.selectOne(queryWrapper);
        if (waitingPlayers.size() >= websocketModel.getMaxConnect()) {
            session.close(CloseStatus.SERVICE_OVERLOAD.withReason("超过设置连接数"));
            return;
        }
        QueryWrapper<UserModel> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.eq("id", queryParams.get("userId").get(0));
        UserModel userModel = userMapper.selectOne(queryWrapper1);
        if (userModel != null) {
            List<ExamModel> exams=null;
            try{
//            判断有没有购买考试
                if(StrUtil.isBlank(queryParams.get("userId").get(0)))
                    exams= new ArrayList<>();
                //查询用户购买的科目
                List<UserAssetsDataModel> userAssetsDataModels = userAssetsDataService.selectList(Wrappers.<UserAssetsDataModel>lambdaQuery()
                        .eq(UserAssetsDataModel::getUserId, queryParams.get("userId").get(0)).eq(UserAssetsDataModel::getAssetType, AssetsType.category).eq(UserAssetsDataModel::getDeleted, false).orderByDesc(UserAssetsDataModel::getStatus));
                if(userAssetsDataModels.isEmpty())
                    exams=  new ArrayList<>();

                //查询考试ids
                List<CategoryModel> categoryList = categoryService.selectList(Wrappers.<CategoryModel>lambdaQuery().in(CategoryModel::getId, userAssetsDataModels.stream().map(UserAssetsDataModel::getAssetMappingId).toList()));
                var examIdArray = categoryList.stream().map(CategoryModel::getExamId).toList();
                var examList = examService.selectList(Wrappers.<ExamModel>lambdaQuery().in(ExamModel::getId, examIdArray));
                //转载考试下面的已购科目
                examList.forEach(c ->c.setCategorys(categoryList.stream().filter(v -> v.getExamId().equals(c.getId())).toList()));
                exams=  examList;
            }catch (Exception e){
                session.close(CloseStatus.BAD_DATA.withReason("您还没购买相应考试"));
                return ;
            }
            boolean buy = exams.stream().anyMatch(c -> c.getId().equals(queryParams.get("examId").get(0)));
            if (!buy){
                session.close(CloseStatus.BAD_DATA.withReason("您还没购买相应考试"));
                return ;
            }
//            自己不和自己匹配
            addToMatchQueue(session, userModel, queryParams.get("examId").get(0), PkType.valueOf(queryParams.get("type").get(0)));
        } else {
            session.close(CloseStatus.BAD_DATA.withReason("非法用户"));
        }
    }

    /**
     * 接收发送的消息，心跳检测也会走这里
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        if (isHeartbeatMessage(message.getPayload())) {
            Set<Map.Entry<WebSocketSession, Long>> entries = sessionLastActiveTime.entrySet();
            for (Map.Entry<WebSocketSession, Long> entry : entries) {
                if (entry.getKey().equals(session)) {
                    entry.setValue(System.currentTimeMillis());
                    break;
                }
            }
            session.sendMessage(new TextMessage("ok"));
        } else {
//            处理正常的客户端消息，客户端消息不外乎获取试题集合，保存做题记录，获取跟对手的对错结果展示分数，做完后提交所有记录，获取结果查看胜负

            try {
                JSONObject request = JSONObject.parseObject(message.getPayload());

                // 获取消息类型
                String status = (String) request.get("status");
                PkStatus pkStatus = PkStatus.valueOf(status);
                // 根据不同状态处理消息
                switch (pkStatus) {
                    case match_success:
                        break;
                    case match_fail:
                        handleMatchFail(session, request);
                        break;
                    case ready:
                        handleReady(session, request);
                        break;
                    case match_waiting:
                        handleMatchWaiting(session, request);
                        break;
                    case disconnection:
                        handleDisconnection(session, request);
                        break;
                    case answer_result:
                        handleAnswerResult(session, request);
                        break;
                    case answer_send:
                        handleAnswerSend(session, request);
                        break;
                    case question_send:
                        handleQuestionSend(session, request);
                        break;
                    case save_one:
                        handleSaveOne(session, request);
                        break;
                    case give_up:
                        handleGiveUp(session, request);
                        break;
                    default:
                        session.sendMessage(new TextMessage("{\"error\":\"Unknown status\"}"));
                        break;
                }
            } catch (Exception e) {
                e.printStackTrace();
                session.sendMessage(new TextMessage("{\"error\":\"Message processing failed\"}"));
            }
        }
    }


    /**
     * 没删除房间，异常
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        waitingPlayers.remove(session);
        sessionLastActiveTime.remove(session);
        Integer roomId = (Integer) session.getAttributes().get("roomId");


        if (status.getReason() != null && status.equals(CloseStatus.GOING_AWAY) && status.getReason().equals("Heartbeat timeout")) {
//            超时断开，通知对方

            if (roomId != null) {
//                清除定时器,生成记录
                String otherUserId = (String) session.getAttributes().get("otherUserId");
                Map<String, RoomQuestionStateVo> stringRoomQuestionStateVoMap = roomQuestionStateMap.get(roomId);
                if (stringRoomQuestionStateVoMap != null) {
                    for (Map.Entry<String, RoomQuestionStateVo> entry : stringRoomQuestionStateVoMap.entrySet()) {
                        RoomQuestionStateVo roomQuestionStateVo = entry.getValue();
                        synchronized (roomQuestionStateVo) {
                            if (roomQuestionStateVo.getTimeoutTask() != null) {
                                roomQuestionStateVo.getTimeoutTask().cancel(true);
                                roomQuestionStateVo.setTimeoutTask(null);
                            }
                        }
                    }
                    roomQuestionStateMap.remove(roomId);
                }
                if (otherUserId != null) {
                    String userId = (String) session.getAttributes().get("userId");
                    String examId = (String) session.getAttributes().get("examId");
                    PkRoom pkRoom = pkRoomMapper.getPkRoom((Integer) session.getAttributes().get("roomId"));
                    if (pkRoom != null) {
                        pkRoomMapper.deleteById(pkRoom.getId());

                        for (WebSocketSession s : waitingPlayers) {
                            if (s.getAttributes().get("userId").equals(otherUserId)) {
                                if (s.isOpen()) {
                                    s.close(CloseStatus.NORMAL.withReason(notify_give_up_text));
                                }
                                break;
                            }
                        }

                        long epochMilli = pkRoom.getStartTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
                        int i = pkRoom.getPerson1().compareTo(pkRoom.getPerson2());
                        String s = "";
                        if (i < 0) {
                            s = new String(Objects.requireNonNull(SecureUtil.md5((epochMilli + "" + pkRoom.getPerson1() + pkRoom.getPerson2()).getBytes())));
                        } else {
                            s = new String(Objects.requireNonNull(SecureUtil.md5((epochMilli + "" + pkRoom.getPerson2() + pkRoom.getPerson1()).getBytes())));
                        }
//                    生成对战记录
                        PkRecords pkRecords = PkRecords.builder()
                                .createTime(LocalDateTime.now())
                                .giveUp(true)
                                .examId(examId)
                                .userId(userId)
                                .startTime(pkRoom.getStartTime())
                                .otherId(otherUserId)
                                .success(otherUserId)

                                .score(pkRoom.getScore1())
                                .otherScore(pkRoom.getScore2())
                                .userPkData(pkRoom.getData1())
                                .otherPkData(pkRoom.getData2())

                                .sign(s)
                                .build();
                        if (pkRoom.getPerson1().equals(userId)) {
                            pkRecords.setUserName(pkRoom.getPerson1Name());
                            pkRecords.setOtherName(pkRoom.getPerson2Name());
                        } else {
                            pkRecords.setUserName(pkRoom.getPerson2Name());
                            pkRecords.setOtherName(pkRoom.getPerson1Name());
                        }
//                    如果同时关闭，不能插入相同的数据，怎么判定是不是已经完成了？同样的完成时间，同样的对手，视为同一条记录
//                    除非所有的房间同时结束，或者有大量的房间导致同时插入的概率增加，不然阻塞不会太严重
//                    时间戳加考试加用户加对手，md5加密生成唯一索引
                        try {
                            pkRecordsMapper.insert(pkRecords);
                        } catch (Exception e) {
                            throw new RuntimeException("已有记录，不用重复添加");
                        }
                    }
                }
                roomLocks.remove( roomId);
                return;
            }
        }
//        放弃则判定对手赢
        if (status.getReason() != null && status.getReason().equals(give_up_text)) {
            String otherUserId = (String) session.getAttributes().get("otherUserId");
            if (otherUserId != null) {
                String userId = (String) session.getAttributes().get("userId");
                String examId = (String) session.getAttributes().get("examId");
                PkRoom pkRoom = pkRoomMapper.getPkRoom((Integer) session.getAttributes().get("roomId"));
                if (pkRoom != null) {
                    pkRoomMapper.deleteById(pkRoom.getId());
                    Map<String, RoomQuestionStateVo> stringRoomQuestionStateVoMap = roomQuestionStateMap.get(pkRoom.getId());
                    if (stringRoomQuestionStateVoMap != null) {
                        for (Map.Entry<String, RoomQuestionStateVo> entry : stringRoomQuestionStateVoMap.entrySet()) {
                            RoomQuestionStateVo roomQuestionStateVo = entry.getValue();
                            synchronized (roomQuestionStateVo) {
                                if (roomQuestionStateVo.getTimeoutTask() != null) {
                                    roomQuestionStateVo.getTimeoutTask().cancel(true);
                                    roomQuestionStateVo.setTimeoutTask(null);
                                }
                            }
                        }
                        roomQuestionStateMap.remove(pkRoom.getId());
                    }
                    for (WebSocketSession s : waitingPlayers) {
                        if (s.getAttributes().get("userId").equals(otherUserId)) {
                            if (s.isOpen()) {
                                s.close(CloseStatus.NORMAL.withReason(notify_give_up_text));
                            }
                            break;
                        }
                    }

                    long epochMilli = pkRoom.getStartTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
                    int i = pkRoom.getPerson1().compareTo(pkRoom.getPerson2());
                    String s = "";
                    if (i < 0) {
                        s = new String(Objects.requireNonNull(SecureUtil.md5((epochMilli + "" + pkRoom.getPerson1() + pkRoom.getPerson2()).getBytes())));
                    } else {
                        s = new String(Objects.requireNonNull(SecureUtil.md5((epochMilli + "" + pkRoom.getPerson2() + pkRoom.getPerson1()).getBytes())));
                    }
//                    生成对战记录
                    PkRecords pkRecords = PkRecords.builder()
                            .createTime(LocalDateTime.now())
                            .giveUp(true)
                            .examId(examId)
                            .userId(userId)
                            .startTime(pkRoom.getStartTime())
                            .otherId(otherUserId)
                            .success(otherUserId)

                            .score(pkRoom.getScore1())
                            .otherScore(pkRoom.getScore2())
                            .userPkData(pkRoom.getData1())
                            .otherPkData(pkRoom.getData2())

                            .sign(s)
                            .build();
                    if (pkRoom.getPerson1().equals(userId)) {
                        pkRecords.setUserName(pkRoom.getPerson1Name());
                        pkRecords.setOtherName(pkRoom.getPerson2Name());
                    } else {
                        pkRecords.setUserName(pkRoom.getPerson2Name());
                        pkRecords.setOtherName(pkRoom.getPerson1Name());
                    }
//                    如果同时关闭，不能插入相同的数据，怎么判定是不是已经完成了？同样的完成时间，同样的对手，视为同一条记录
//                    除非所有的房间同时结束，或者有大量的房间导致同时插入的概率增加，不然阻塞不会太严重
//                    时间戳加考试加用户加对手，md5加密生成唯一索引
                    try {
                        pkRecordsMapper.insert(pkRecords);
                    } catch (Exception e) {
                        throw new RuntimeException("已有记录，不用重复添加");
                    }
                }
            }
            roomLocks.remove( roomId);
            return;
        }
        if (status.getReason() != null && status.getReason().equals(notify_give_up_text)) {
            return;
        }
//             CloseStatus.NORMAL 跳转到其他页面或关闭浏览器，正常关闭，可以等待重连

        PkRoom pkRoom = null;
        if (roomId != null) {
            pkRoom = pkRoomMapper.getPkRoom(roomId);
        }
        if (pkRoom != null) {
            if (pkRoom.getSessionId1() == null || pkRoom.getSessionId2() == null) {
                pkRoomMapper.deleteById(pkRoom.getId());
                Map<String, RoomQuestionStateVo> stringRoomQuestionStateVoMap = roomQuestionStateMap.get(pkRoom.getId());
                for (Map.Entry<String, RoomQuestionStateVo> entry : stringRoomQuestionStateVoMap.entrySet()) {
                    RoomQuestionStateVo roomQuestionStateVo = entry.getValue();
                    synchronized (getRoomLock(roomId)) {
                        if (roomQuestionStateVo.getTimeoutTask() != null) {
                            roomQuestionStateVo.getTimeoutTask().cancel(true);
                            roomQuestionStateVo.setTimeoutTask(null);
                        }
                    }
                }
                roomQuestionStateMap.remove(pkRoom.getId());
                roomLocks.remove( roomId);
            } else {
//                Map<String, RoomQuestionStateVo> stringRoomQuestionStateVoMap = roomQuestionStateMap.get(pkRoom.getId());
//                for (Map.Entry<String, RoomQuestionStateVo> entry : stringRoomQuestionStateVoMap.entrySet()) {
//                    RoomQuestionStateVo roomQuestionStateVo = entry.getValue();
//                    synchronized (roomQuestionStateVo) {
//                        if (roomQuestionStateVo.getTimeoutTask() != null) {
//                            roomQuestionStateVo.getTimeoutTask().cancel(true);
//                            roomQuestionStateVo.setTimeoutTask(null);
//                        }
//                    }
//                }


                UpdateWrapper<PkRoom> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("id", pkRoom.getId());

                String otherUserId = "";
                if (session.getId().equals(pkRoom.getSessionId1())) {
                    pkRoom.setSessionId1(null);
                    updateWrapper.set("session_id1", null);
                    otherUserId = pkRoom.getSessionId2();
                } else {
                    pkRoom.setSessionId2(null);
                    updateWrapper.set("session_id2", null);
                    otherUserId = pkRoom.getSessionId1();
                }


//                并发时，没有影响
//                这里有一个额外的操作，通知对手已离线
                for (WebSocketSession waitingPlayer : waitingPlayers) {
                    if (waitingPlayer.getAttributes().get("userId").equals(otherUserId)) {
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put("message", "您的对手已离线");
                        sendMessage(waitingPlayer, pkRoom.getId(), PkStatus.other_not_online, pkRoom.getType(), jsonObject);
                        break;
                    }
                }
                pkRoomMapper.update(pkRoom, updateWrapper);
            }
        }
    }

    // 获取当前连接数
    public int getCurrentConnectionCount() {
        return waitingPlayers.size();
    }

    // 处理题目发送请求
    private void handleQuestionSend(WebSocketSession session, JSONObject request) throws IOException {
        String userId = (String) session.getAttributes().get("userId");
        Integer roomId = (Integer) request.get("roomId");
        QueryWrapper<PkRoom> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", roomId);
        PkRoom pkRoom = pkRoomMapper.getPkRoom(roomId);

        if (pkRoom != null) {
            request.put("status", PkStatus.question_send.name());

            List<Map<String, Object>> list = pkRoom.getQuestions().stream().peek(e -> {
                e.remove("answer");
                e.remove("answerDesc");
//                e.remove("quOption");
//                e.remove("stem");
                e.remove("categoryId");
            }).toList();
            request.put("data", list);
            // 将题目列表转换为JSON
            session.sendMessage(new TextMessage(request.toJSONString()));
        }
    }

    /**
     * 双方都准备好了，才开始倒计时
     * 什么时候取消已准备好的属性，如果关闭了连接，对手的准备状态呢？
     * 开始时返回第一道题
     * 获取分数，做题数
     * 约定字段 myScore,myNum|otherScore,otherNum
     * 有可能有重连的
     */
    private void handleReady(WebSocketSession session, JSONObject request) {
        session.getAttributes().put("ready", true);
//        String questionId = (String) request.get("questionId");
        PkType pkType = (PkType) session.getAttributes().get("type");
        Integer roomId = (Integer) session.getAttributes().get("roomId");
//            查询对手是否已经准备好
        for (WebSocketSession waitingPlayer : waitingPlayers) {
            if (waitingPlayer.getAttributes().get("userId").equals(session.getAttributes().get("otherUserId"))) {
                Boolean b = (Boolean) waitingPlayer.getAttributes().get("ready");
                if (b != null && b) {
                    boolean isPerson1 = false;
                    JSONObject jsonObject1 = new JSONObject();
                    JSONObject jsonObject2 = new JSONObject();


//                    先查询数据库startTime是否已经写入
                    QueryWrapper<PkRoom> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("id", roomId);

                    synchronized (getRoomLock(roomId)) {
                        //                    如果查询的时候刚好定时任务提交呢，两种情况，先读后改，先改后读
                        PkRoom oldRoom = pkRoomMapper.selectOne(queryWrapper);
                        int minIndex = Math.min(oldRoom.getDoNum1(), oldRoom.getDoNum2());
                        String questionId = (String) oldRoom.getQuestions().get(minIndex).get("id");
                        RoomQuestionStateVo roomQuestionStateVo = roomQuestionStateMap.get(roomId).get(questionId);
                        Boolean reConnect = (Boolean) request.get("reConnect");
//                    if (!reConnect) {
//                        if (roomQuestionStateVo.getTime() == null) {
//                            roomQuestionStateVo.setTime(System.currentTimeMillis());
//
//                            if (roomQuestionStateVo.getTimeoutTask() == null) {
//                                ScheduledFuture<?> timeoutTask = scheduler.schedule(() -> {
//                                    // 超时处理逻辑
//                                    handleQuestionTimeout(pkType, questionId, oldRoom.getPerson1(), oldRoom.getPerson2(), roomId);
//                                }, 20, TimeUnit.SECONDS);
//                                roomQuestionStateVo.setTimeoutTask(timeoutTask);
//                            }
//                        }
//                    }

                        if (roomQuestionStateVo.getTime() == null) {
                            roomQuestionStateVo.setTime(System.currentTimeMillis());
                        }

//                    如果ready时刚好执行定时任务呢
                        // 取消所有已存在的定时任务
                        for (RoomQuestionStateVo stateVo : roomQuestionStateMap.get(roomId).values()) {
                            ScheduledFuture<?> existingTask = stateVo.getTimeoutTask();
                            if (existingTask != null) {
                                existingTask.cancel(true);
                                stateVo.setTimeoutTask(null);
                            }
//                        scheduler.submit()
                        }
                        // 重新设置定时任务
                        if (roomQuestionStateVo.getTimeoutTask() == null) {
                            double ceil = Math.ceil((System.currentTimeMillis() - roomQuestionStateVo.getTime()) / 1000);
                            int second = (int) ceil;
//
//                        不能并发设置定时任务

                            ScheduledFuture<?> timeoutTask = scheduler.schedule(() -> {
                                handleQuestionTimeout(pkType, questionId, oldRoom.getPerson1(), oldRoom.getPerson2(), roomId);
                            }, Math.max(20 - second, 0), TimeUnit.SECONDS);
                            roomQuestionStateVo.setTimeoutTask(timeoutTask);
                        }

                        if (oldRoom.getPerson1().equals(session.getAttributes().get("userId"))) {
                            isPerson1 = true;
                        }
                        if (isPerson1) {
                            jsonObject1.put("myNum", oldRoom.getDoNum1());
                            jsonObject1.put("myScore", oldRoom.getScore1());

                            jsonObject1.put("otherNum", oldRoom.getDoNum2());
                            jsonObject1.put("otherScore", oldRoom.getScore2());

                            jsonObject2.put("myNum", oldRoom.getDoNum2());
                            jsonObject2.put("myScore", oldRoom.getScore2());

                            jsonObject2.put("otherNum", oldRoom.getDoNum1());
                            jsonObject2.put("otherScore", oldRoom.getScore1());

                        } else {
                            jsonObject2.put("myNum", oldRoom.getDoNum1());
                            jsonObject2.put("myScore", oldRoom.getScore1());

                            jsonObject2.put("otherNum", oldRoom.getDoNum2());
                            jsonObject2.put("otherScore", oldRoom.getScore2());

                            jsonObject1.put("myNum", oldRoom.getDoNum2());
                            jsonObject1.put("myScore", oldRoom.getScore2());

                            jsonObject1.put("otherNum", oldRoom.getDoNum1());
                            jsonObject1.put("otherScore", oldRoom.getScore1());
                        }
//                    最后一题有倒计时的索引
                        jsonObject1.put("time", roomQuestionStateVo.getTime());
                        jsonObject2.put("time", roomQuestionStateVo.getTime());

                        jsonObject1.put("index", minIndex);
                        jsonObject2.put("index", minIndex);
                        LocalDateTime now = LocalDateTime.now();
                        if (oldRoom.getStartTime() != null) {
                            now = oldRoom.getStartTime();
                        } else {
                            UpdateWrapper<PkRoom> updateWrapper = new UpdateWrapper<>();
                            updateWrapper.eq("id", roomId).set("start_time", now);
                            pkRoomMapper.update(new PkRoom(), updateWrapper);
                        }
                        long epochMilli = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
                        jsonObject1.put("startTime", epochMilli);
                        jsonObject2.put("startTime", epochMilli);

                        try {
//                            时间戳问题，如何统一
//                            如果是普通模式
                            synchronized (session) {
                                sendMessage(session, (Integer) request.get("roomId"), PkStatus.ready, pkType, jsonObject1);
                            }
                            if (!reConnect) {
                                synchronized (waitingPlayer) {
                                    sendMessage(waitingPlayer, (Integer) request.get("roomId"), PkStatus.ready, pkType, jsonObject2);
                                }
                            }
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                        break;

                    }


                }
            }
        }

    }


    // 处理答案发送
    private void handleAnswerSend(WebSocketSession session, JSONObject request) throws IOException {
//        String userId = (String) session.getAttributes().get("userId");
//        Integer roomId = request.get("roomId").getAsInt();
//        String questionId = request.get("questionId").getAsString();
//        String answer = request.get("answer").getAsString();
//        String time = request.get("time").getAsString();
//
//        // 更新答题记录
//        QueryWrapper<PkRoom> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("id", roomId);
//        PkRoom pkRoom = pkRoomMapper.selectOne(queryWrapper);
//
//        if (pkRoom != null) {
//            PkData pkData = new PkData(questionId, answer, time, true);
//
//            if (pkRoom.getPerson1().equals(userId)) {
//                // 更新玩家1的数据
//                if (pkRoom.getData1() == null) {
//                    pkRoom.setData1(new ArrayList<>());
//                }
//                pkRoom.getData1().add(pkData);
//            } else {
//                // 更新玩家2的数据
//                if (pkRoom.getData2() == null) {
//                    pkRoom.setData2(new ArrayList<>());
//                }
//                pkRoom.getData2().add(pkData);
//            }
//
//            // 更新数据库
//            pkRoomMapper.updateById(pkRoom);
//
//            // 通知对手答案已提交
//            notifyOpponentAnswerSubmitted(pkRoom, userId, questionId, session.getId());
//        }
//
//        JsonObject response = new JsonObject();
//        response.addProperty("status", PkStatus.answer_send.name());
//        response.addProperty("message", "答案已提交");
//        session.sendMessage(new TextMessage(response.toString()));
    }

    // 处理答案结果
    private void handleAnswerResult(WebSocketSession session, JSONObject request) throws IOException {
//        String userId = (String) session.getAttributes().get("userId");
//        Integer roomId = request.get("roomId").getAsInt();
//
//        // 查询房间和对手信息
//        QueryWrapper<PkRoom> queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("id", roomId);
//        PkRoom pkRoom = pkRoomMapper.selectOne(queryWrapper);
//
//        if (pkRoom != null) {
//            JsonObject response = new JsonObject();
//            response.addProperty("status", PkStatus.answer_result.name());
//            response.addProperty("roomId", roomId);
//
//            // 计算双方得分
//            int score1 = calculateScore(pkRoom.getData1());
//            int score2 = calculateScore(pkRoom.getData2());
//
//            response.addProperty("score1", score1);
//            response.addProperty("score2", score2);
//
//            // 判断胜负
//            String result;
//            if (score1 > score2) {
//                result = pkRoom.getPerson1().equals(userId) ? "胜利" : "失败";
//            } else if (score1 < score2) {
//                result = pkRoom.getPerson2().equals(userId) ? "胜利" : "失败";
//            } else {
//                result = "平局";
//            }
//            response.addProperty("result", result);
//
//            session.sendMessage(new TextMessage(response.toString()));
//        }
    }


    private void handleGiveUp(WebSocketSession session, JSONObject request) {
//        立即关闭当前的连接，同时通知对手，
        try {
            session.close(CloseStatus.NORMAL.withReason(give_up_text));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 处理题目超时
     * 定时任务，超时跳转下一道题，如果没有下一道题，则交卷结束
     * 计算做题数量，需要额外考虑
     */
    @Transactional
    public void handleQuestionTimeout(PkType pkType, String currentQuestionId, String userId1, String userId2, Integer roomId) {
        synchronized (getRoomLock(roomId)) {
            Map<String, RoomQuestionStateVo> stringRoomQuestionStateVoMap = roomQuestionStateMap.get(roomId);
            RoomQuestionStateVo roomQuestionStateVo = stringRoomQuestionStateVoMap.get(currentQuestionId);

            boolean player1Submitted = roomQuestionStateVo.getPlayer1Submitted();
            boolean player2Submitted = roomQuestionStateVo.getPlayer2Submitted();

            roomQuestionStateVo.setPlayer1Submitted(true);
            roomQuestionStateVo.setPlayer2Submitted(true);
            PkRoom pkRoom = pkRoomMapper.getPkRoom(roomId);
            List<Map<String, Object>> questions = pkRoom.getQuestions();

            int currentIndex = 0;
            String nextQuestionId = null;
            for (int i = 0; i < questions.size(); i++) {
                var question = questions.get(i);
                if (question.get("id").equals(currentQuestionId)) {
                    currentIndex = i;
                    break;
                }
            }
            if (currentIndex < questions.size() - 1) {
                nextQuestionId = (String) questions.get(currentIndex + 1).get("id");
            }
            if (nextQuestionId != null) {
                RoomQuestionStateVo roomQuestionStateVo1 = stringRoomQuestionStateVoMap.get(nextQuestionId);
                long l = System.currentTimeMillis();
                if (roomQuestionStateVo1.getTime() == null) {
                    roomQuestionStateVo1.setTime(l);
                    final String nextQuestionId1 = nextQuestionId;

                    if (roomQuestionStateVo1.getTimeoutTask() == null) {
                        ScheduledFuture<?> schedule = scheduler.schedule(() -> handleQuestionTimeout(pkType, nextQuestionId1, userId1, userId2, roomId), 20, TimeUnit.SECONDS);
                        roomQuestionStateVo1.setTimeoutTask(
                                schedule
                        );
                    }
                }

                if (roomQuestionStateVo.getPlayer1Id().equals(pkRoom.getPerson1())) {
                    pkRoom.setDoNum1(player1Submitted ? pkRoom.getDoNum1() : pkRoom.getDoNum1() + 1);
                    pkRoom.setDoNum2(player2Submitted ? pkRoom.getDoNum2() : pkRoom.getDoNum2() + 1);
                } else {
                    pkRoom.setDoNum1(player2Submitted ? pkRoom.getDoNum1() : pkRoom.getDoNum1() + 1);
                    pkRoom.setDoNum2(player1Submitted ? pkRoom.getDoNum2() : pkRoom.getDoNum2() + 1);
                }
                UpdateWrapper<PkRoom> pkRoomQueryWrapper = new UpdateWrapper<>();
                pkRoomQueryWrapper.eq("id", roomId).set("do_num1", pkRoom.getDoNum1()).set("do_num2", pkRoom.getDoNum2()).set("finished", true);
                pkRoomMapper.update(pkRoom, pkRoomQueryWrapper);

                JSONObject jsonObject = new JSONObject();

                jsonObject.put("stage", "normal");
//            jsonObject.put("question", questions.get(currentIndex + 1));
                jsonObject.put("myNum", pkRoom.getDoNum1());
                jsonObject.put("myScore", pkRoom.getScore1());
                jsonObject.put("otherNum", pkRoom.getDoNum2());
                jsonObject.put("otherScore", pkRoom.getScore2());


                JSONObject jsonObject1 = new JSONObject();
                jsonObject1.put("stage", "normal");
//            jsonObject1.put("question", questions.get(currentIndex + 1));
                jsonObject1.put("myNum", pkRoom.getDoNum2());
                jsonObject1.put("myScore", pkRoom.getScore2());
                jsonObject1.put("otherNum", pkRoom.getDoNum1());
                jsonObject1.put("otherScore", pkRoom.getScore1());


                jsonObject.put("time", l);
                jsonObject1.put("time", l);


                for (WebSocketSession session : waitingPlayers) {
                    if (session.getAttributes().get("roomId") != null && session.getAttributes().get("roomId").equals(roomId)) {
                        try {
                            synchronized (session) {
                                if (pkRoom.getPerson1().equals(session.getAttributes().get("userId"))) {
                                    sendMessage(session, roomId, PkStatus.save_one, pkType, jsonObject);
                                } else if (pkRoom.getPerson2().equals(session.getAttributes().get("userId"))) {
                                    sendMessage(session, roomId, PkStatus.save_one, pkType, jsonObject1);
                                }
                            }
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            } else {
                String s = "";
                int compare = pkRoom.getPerson1().compareTo(pkRoom.getPerson2());
                if (compare < 0) {
                    s = new String(Objects.requireNonNull(SecureUtil.md5((pkRoom.getStartTime() + "" + pkRoom.getPerson1() + pkRoom.getPerson2()).getBytes())));
                } else {
                    s = new String(Objects.requireNonNull(SecureUtil.md5((pkRoom.getStartTime() + "" + pkRoom.getPerson2() + pkRoom.getPerson1()).getBytes())));
                }
                PkRecords pkRecords = PkRecords.builder()
                        .createTime(LocalDateTime.now())
                        .giveUp(false)
                        .examId(pkRoom.getExamId())
                        .userId(pkRoom.getPerson1())
                        .userName(pkRoom.getPerson1Name())
                        .userPkData(pkRoom.getData1())
                        .score(pkRoom.getScore1())

                        .startTime(pkRoom.getStartTime())
                        .otherId(pkRoom.getPerson2())
                        .otherName(pkRoom.getPerson2Name())
                        .otherPkData(pkRoom.getData2())
                        .otherScore(pkRoom.getScore2())
                        .sign(s)

                        .build();
                if (pkRoom.getScore1().equals(pkRoom.getScore2())) {
                    pkRecords.setSuccess(null);
                } else if (pkRoom.getScore1() > pkRoom.getScore2()) {
                    pkRecords.setSuccess(pkRoom.getPerson1());
                } else {
                    pkRecords.setSuccess(pkRoom.getPerson2());
                }
//            生成记录并返回提示结束
                try {
                    pkRecordsMapper.insert(pkRecords);
                    pkRoomMapper.deleteById(pkRoom);
                    for (WebSocketSession session : waitingPlayers) {
                        if (session.getAttributes().get("userId").equals(userId1)) {
                            JSONObject response1 = new JSONObject();
                            if (pkRoom.getScore1().equals(pkRoom.getScore2())) {
                                response1.put("result", "平局");
                            } else {
                                if (pkRoom.getScore1() > pkRoom.getScore2()) {
                                    response1.put("result", "你赢了");
                                } else {
                                    response1.put("result", "你输了");
                                }
                            }
                            sendMessage(session, roomId, PkStatus.finished, pkType, response1);
                        } else if (session.getAttributes().get("userId").equals(userId2)) {
                            JSONObject response2 = new JSONObject();
                            if (pkRoom.getScore1().equals(pkRoom.getScore2())) {
                                response2.put("result", "平局");
                            } else {
                                if (pkRoom.getScore2() > pkRoom.getScore1()) {
                                    response2.put("result", "你赢了");
                                } else {
                                    response2.put("result", "你输了");
                                }
                            }
                            sendMessage(session, roomId, PkStatus.finished, pkType, response2);
                        }
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    //    不允许刷新状态下重新提交,前后端都校验,如果前端没有明确指示非重新提交，则后端再次校验一次
    private void handleSaveOne(WebSocketSession session, JSONObject request) {
        String nextQuestionId = (String) request.get("nextQuestionId");
        Integer roomId = (Integer) request.get("roomId");
        String questionId = (String) request.get("questionId");
        PkType pkType = (PkType) session.getAttributes().get("type");
        synchronized (getRoomLock(roomId)) {
            if (pkType.equals(PkType.common)) {
                PkRoom pkRoom = pkRoomMapper.getPkRoom(roomId);

                RoomQuestionStateVo roomQuestionStateVo = roomQuestionStateMap.get(roomId).get(questionId);
                boolean isPlayer1 = roomQuestionStateVo.getPlayer1Id().equals(session.getAttributes().get("userId"));
                if (isPlayer1) {
                    if (roomQuestionStateVo.getPlayer1Submitted()) {
                        throw new RuntimeException("请勿重复提交答案");
                    }
                    roomQuestionStateVo.setPlayer1Submitted(true);
                } else {
                    if (roomQuestionStateVo.getPlayer2Submitted()) {
                        throw new RuntimeException("请勿重复提交答案");
                    }
                    roomQuestionStateVo.setPlayer2Submitted(true);
                }
                if (request.get("stage").equals("normal")) {
                    JSONObject response1 = new JSONObject();
                    JSONObject response2 = new JSONObject();

                    response1.put("stage", "normal");
                    response2.put("stage", "normal");

//                判断对错,取出的多选题answer可能是list也可能是String
//                    todo jsonArray jsonObject
                    Object answer = roomQuestionStateVo.getAnswer();
//                    cn.hutool.json.JSONArray
                    boolean currentRight = false;
                    if (roomQuestionStateVo.getType().equals(QuestionType.multipleChoice)) {
                        cn.hutool.json.JSONArray myAnswer = new cn.hutool.json.JSONArray(request.get("answer"));

                        currentRight = compareJsonArrays(myAnswer, (cn.hutool.json.JSONArray) answer);
                    } else {
                        if (request.get("answer").equals(answer)) {
                            currentRight = true;
                        }
                    }


                    boolean isPerson1 = false;
//                如果对应person1
                    if (session.getAttributes().get("userId").equals(pkRoom.getPerson1())) {
                        isPerson1 = true;
                    }
                    if (currentRight) {
                        if (isPerson1) {
                            if (roomQuestionStateVo.getType() == QuestionType.multipleChoice) {
                                pkRoom.setScore1(pkRoom.getScore1() + 2);
                            } else {
                                pkRoom.setScore1(pkRoom.getScore1() + 1);
                            }
                        } else {
                            if (roomQuestionStateVo.getType() == QuestionType.multipleChoice) {
                                pkRoom.setScore2(pkRoom.getScore2() + 2);
                            } else {
                                pkRoom.setScore2(pkRoom.getScore2() + 1);
                            }
                        }
                    }

                    if (isPerson1) {
                        pkRoom.setDoNum1(pkRoom.getDoNum1() + 1);
                        List<PkData> data1 = pkRoom.getData1();
                        if (data1 == null) {
                            List<PkData> pkData = new ArrayList<>();
                            pkData.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                            pkRoom.setData1(pkData);
                        } else {
                            data1.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                        }


                        response1.put("myScore", pkRoom.getScore1());
                        response1.put("otherScore", pkRoom.getScore2());
                        response1.put("myNum", pkRoom.getDoNum1());
                        response1.put("otherNum", pkRoom.getDoNum2());
                        response1.put("answer", roomQuestionStateVo.getAnswer());


                        response2.put("myScore", pkRoom.getScore2());
                        response2.put("otherScore", pkRoom.getScore1());
                        response2.put("myNum", pkRoom.getDoNum2());
                        response2.put("otherNum", pkRoom.getDoNum1());
                        response2.put("answer", roomQuestionStateVo.getAnswer());
                        response2.put("otherAnswer", request.get("answer"));
                        if (pkRoom.getData2() != null) {
                            for (PkData data : pkRoom.getData2()) {
                                if (data.getQuestionId().equals(roomQuestionStateVo.getQuestionId())) {
                                    response1.put("otherAnswer", data.getAnswer());
                                    break;
                                }
                            }
                        }
                    } else {
                        pkRoom.setDoNum2(pkRoom.getDoNum2() + 1);
                        List<PkData> data1 = pkRoom.getData2();
                        if (data1 == null) {
                            List<PkData> pkData = new ArrayList<>();
                            pkData.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                            pkRoom.setData2(pkData);
                        } else {
                            data1.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                        }
                        response1.put("myScore", pkRoom.getScore2());
                        response1.put("otherScore", pkRoom.getScore1());
                        response1.put("myNum", pkRoom.getDoNum2());
                        response1.put("otherNum", pkRoom.getDoNum1());
                        response1.put("answer", roomQuestionStateVo.getAnswer());

                        response2.put("myScore", pkRoom.getScore1());
                        response2.put("otherScore", pkRoom.getScore2());
                        response2.put("myNum", pkRoom.getDoNum1());
                        response2.put("otherNum", pkRoom.getDoNum2());
                        response2.put("answer", roomQuestionStateVo.getAnswer());
                        response2.put("otherAnswer", request.get("answer"));
                        if (pkRoom.getData1() != null) {
                            for (PkData data : pkRoom.getData1()) {
                                if (data.getQuestionId().equals(roomQuestionStateVo.getQuestionId())) {
                                    response1.put("otherAnswer", data.getAnswer());
                                    break;
                                }
                            }
                        }
                    }

                    //                        做题完成,计算双方分数，更新进度，返回下一题数据，开始下一题
                    if (roomQuestionStateVo.getPlayer1Submitted() && roomQuestionStateVo.getPlayer2Submitted()) {
                        pkRoom.setFinished(true);
                    }
                    UpdateWrapper<PkRoom> updateWrapper = new UpdateWrapper<>();
                    updateWrapper.eq("id", pkRoom.getId())
//                        .set("score1", pkRoom.getScore1())
//                        .set("score2", pkRoom.getScore2())
//                        .set("do_num1", pkRoom.getDoNum1())
//                        .set("do_num2", pkRoom.getDoNum2())
//                        .set("data1", pkRoom.getData1())
//                        .set("data2", pkRoom.getData2())
//                        .set("finished", pkRoom.getFinished())
                    ;
                    int i = 0;
                    try {
                        i = pkRoomMapper.update(pkRoom, updateWrapper);
                    } catch (Exception e) {
                        if (isPerson1) {
                            roomQuestionStateVo.setPlayer1Submitted(false);
                        } else {
                            roomQuestionStateVo.setPlayer2Submitted(false);
                        }
                        log.error("更新异常", e);
                        // 更新异常
                        try {
                            sendMessage(session, roomId, PkStatus.update_error, pkType, new JSONObject());
                        } catch (IOException e1) {
                            throw new RuntimeException(e1);
                        }
                    }

                    if (i > 0) {
                        if (roomQuestionStateVo.getPlayer1Submitted() && roomQuestionStateVo.getPlayer2Submitted()) {
                            RoomQuestionStateVo roomQuestionStateVo1 = roomQuestionStateMap.get(roomId).get(nextQuestionId);
//                        停止当前定时器并设置之后的定时器
                            ScheduledFuture<?> timeoutTask = roomQuestionStateVo.getTimeoutTask();
                            if (timeoutTask != null) {
                                timeoutTask.cancel(true);
                                roomQuestionStateVo.setTimeoutTask(null);
                            }
                            //                设置时间必须要两个人都准备好时,设置下一道题的倒计时

                            if (roomQuestionStateVo1.getTime() == null) {
                                roomQuestionStateVo1.setTime(System.currentTimeMillis());
                                ScheduledFuture<?> timeoutTask1 = scheduler.schedule(() -> {
                                    // 超时处理逻辑
                                    handleQuestionTimeout(PkType.common, nextQuestionId, pkRoom.getPerson1(), pkRoom.getPerson2(), roomId);
                                }, 20, TimeUnit.SECONDS);
                                roomQuestionStateVo.setTimeoutTask(timeoutTask1);
                            }
                            response1.put("time", roomQuestionStateVo1.getTime());
                            response2.put("time", roomQuestionStateVo1.getTime());
                            try {
                                synchronized (session) {
                                    sendMessage(session, roomId, PkStatus.save_one, pkType, response1);
                                }
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                            for (WebSocketSession s : waitingPlayers) {
                                if (s.getAttributes().get("userId").equals(session.getAttributes().get("otherUserId"))) {
                                    try {
                                        synchronized (s) {
                                            sendMessage(s, roomId, PkStatus.save_one, pkType, response2);
                                        }
                                    } catch (IOException e) {
                                        throw new RuntimeException(e);
                                    }
                                    break;
                                }
                            }
                        }
                    }
                } else if (request.get("stage").equals("last")) {
                    JSONObject response1 = new JSONObject();
                    JSONObject response2 = new JSONObject();

                    response1.put("stage", "last");
                    response2.put("stage", "last");

                    Object answer = roomQuestionStateVo.getAnswer();
//                做对了
                    boolean currentRight = false;

                    if (roomQuestionStateVo.getType().equals(QuestionType.multipleChoice)) {
                        cn.hutool.json.JSONArray myAnswer = new cn.hutool.json.JSONArray((String) request.get("answer"));
                        currentRight = compareJsonArrays(myAnswer, (cn.hutool.json.JSONArray) answer);
                    } else {
                        if (request.get("answer").equals(answer)) {
                            currentRight = true;
                        }
                    }
                    boolean isPerson1 = false;
//                如果对应person1
                    if (session.getAttributes().get("userId").equals(pkRoom.getPerson1())) {
                        isPerson1 = true;
                    }
                    if (currentRight) {
                        if (isPerson1) {
                            if (roomQuestionStateVo.getType() == QuestionType.multipleChoice) {
                                pkRoom.setScore1(pkRoom.getScore1() + 2);
                            } else {
                                pkRoom.setScore1(pkRoom.getScore1() + 1);
                            }
                        } else {
                            if (roomQuestionStateVo.getType() == QuestionType.multipleChoice) {
                                pkRoom.setScore2(pkRoom.getScore2() + 2);
                            } else {
                                pkRoom.setScore2(pkRoom.getScore2() + 1);
                            }
                        }
                    }
                    if (isPerson1) {
                        pkRoom.setDoNum1(pkRoom.getDoNum1() + 1);
                        List<PkData> data1 = pkRoom.getData1();
                        if (data1 == null) {
                            List<PkData> pkData = new ArrayList<>();
                            pkData.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                            pkRoom.setData1(pkData);
                        } else {
                            data1.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                        }

                    } else {
                        pkRoom.setDoNum2(pkRoom.getDoNum2() + 1);
                        List<PkData> data1 = pkRoom.getData2();
                        if (data1 == null) {
                            List<PkData> pkData = new ArrayList<>();
                            pkData.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                            pkRoom.setData2(pkData);
                        } else {
                            data1.add(new PkData(questionId, request.get("answer").toString(), "", true, currentRight));
                        }
                    }
                    //                        做题完成,计算双方分数，更新进度，返回下一题数据，开始下一题
                    if (roomQuestionStateVo.getPlayer1Submitted() && roomQuestionStateVo.getPlayer2Submitted()) {
                        pkRoom.setFinished(true);
                    }
                    UpdateWrapper<PkRoom> updateWrapper = new UpdateWrapper<>();
                    updateWrapper.eq("id", pkRoom.getId())
//                        .set("score1", pkRoom.getScore1())
//                        .set("score2", pkRoom.getScore2())
//                        .set("do_num1", pkRoom.getDoNum1())
//                        .set("do_num2", pkRoom.getDoNum2())
//                        .set("data1", pkRoom.getData1())
//                        .set("data2", pkRoom.getData2())
//                        .set("finished", pkRoom.getFinished())
                    ;
                    int i = 0;
                    try {
                        i = pkRoomMapper.update(pkRoom, updateWrapper);
                    } catch (Exception e0) {
                        log.error("更新异常", e0);
                        if (isPerson1) {
                            roomQuestionStateVo.setPlayer1Submitted(false);
                        } else {
                            roomQuestionStateVo.setPlayer2Submitted(false);
                        }
                        // 更新异常
                        try {
                            sendMessage(session, roomId, PkStatus.update_error, pkType, new JSONObject());
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    if (i > 0 && roomQuestionStateVo.getPlayer1Submitted() && roomQuestionStateVo.getPlayer2Submitted()) {
                        if (pkRoom.getScore1().equals(pkRoom.getScore2())) {
                            response1.put("result", "平局");
                            response2.put("result", "平局");
                        } else {
                            if (pkRoom.getScore1() > pkRoom.getScore2()) {
                                response1.put("result", "你赢了");
                                response2.put("result", "你输了");
                            } else {
                                response1.put("result", "你输了");
                                response2.put("result", "你赢了");
                            }
                        }


                        try {
                            sendMessage(session, roomId, PkStatus.finished, pkType, response1);
//                        new Thread(() -> {
//                            try {
//                                Thread.sleep(2000);
//                                session.close();
//                            } catch (InterruptedException e) {
//                                throw new RuntimeException(e);
//                            }
//                        }).start();

                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                        for (WebSocketSession s : waitingPlayers) {
                            if (s.getAttributes().get("userId").equals(session.getAttributes().get("otherUserId"))) {
                                try {
                                    sendMessage(s, roomId, PkStatus.finished, pkType, response2);
                                } catch (IOException e) {
                                    throw new RuntimeException(e);
                                }
                                break;
                            }
                        }
                        //                    生成一条pk记录，结束战斗
                        String s = "";
                        int compare = pkRoom.getPerson1().compareTo(pkRoom.getPerson2());
                        if (compare < 0) {
                            s = new String(Objects.requireNonNull(SecureUtil.md5((pkRoom.getStartTime() + "" + pkRoom.getPerson1() + pkRoom.getPerson2()).getBytes())));
                        } else {
                            s = new String(Objects.requireNonNull(SecureUtil.md5((pkRoom.getStartTime() + "" + pkRoom.getPerson2() + pkRoom.getPerson1()).getBytes())));
                        }
                        try {
                            PkRecords pkRecords = PkRecords.builder()
                                    .createTime(LocalDateTime.now())
                                    .giveUp(false)
                                    .examId(pkRoom.getExamId())
                                    .userId(pkRoom.getPerson1())
                                    .userName(pkRoom.getPerson1Name())
                                    .userPkData(pkRoom.getData1())
                                    .score(pkRoom.getScore1())

                                    .startTime(pkRoom.getStartTime())
                                    .otherId(pkRoom.getPerson2())
                                    .otherName(pkRoom.getPerson2Name())
                                    .otherPkData(pkRoom.getData2())
                                    .otherScore(pkRoom.getScore2())
                                    .sign(s)
                                    .build();
                            if (pkRoom.getScore1().equals(pkRoom.getScore2())) {
                                pkRecords.setSuccess(null);
                            } else if (pkRoom.getScore1() > pkRoom.getScore2()) {
                                pkRecords.setSuccess(pkRoom.getPerson1());
                            } else {
                                pkRecords.setSuccess(pkRoom.getPerson2());
                            }
                            pkRecordsMapper.insert(pkRecords);
                        } catch (Exception e) {
                            throw new RuntimeException("请不要重复保存pk记录");
                        }

                    }
                } else {
                    throw new RuntimeException("非正常提交状态");
                }
            } else {
                //        初始提交，只获取下一道题，同时也返回对手的信息
                if (request.get("stage").equals("initial")) {

                } else if (request.get("stage").equals("normal")) {

                } else if (request.get("stage").equals("last")) {

                } else {
                    throw new RuntimeException("非正常提交状态");
                }
            }
        }

    }

    // 处理匹配失败
    private void handleMatchFail(WebSocketSession session, JSONObject request) throws IOException {

    }

    // 处理匹配等待
    private void handleMatchWaiting(WebSocketSession session, JSONObject request) throws IOException {

    }

    // 处理断开连接
    private void handleDisconnection(WebSocketSession session, JSONObject request) throws IOException {

    }

    /**
     * 比较两个JSONArray是否相等（不考虑元素顺序）
     */
    private boolean compareJsonArrays(cn.hutool.json.JSONArray array1, cn.hutool.json.JSONArray array2) {
        if (array1 == null && array2 == null) {
            return true;
        }
        if (array1 == null || array2 == null) {
            return false;
        }
        if (array1.size() != array2.size()) {
            return false;
        }

        // 创建副本进行排序比较
        List<String> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();

        for (int i = 0; i < array1.size(); i++) {
            list1.add((String) array1.get(i));
        }
        for (int i = 0; i < array2.size(); i++) {
            list2.add((String) array2.get(i));
        }

        Collections.sort(list1);
        Collections.sort(list2);

        return list1.equals(list2);
    }
}

